大家好,我是 Yubin
這篇文章跟大家介紹在開發後端網路應用程式的時候,非常重要的環節,快取 (Cache)。
Cache 快取是什麼?
想像一下,你在第一次瀏覽某個網站的時候,可能圖片載入速度很慢,但當你重新整理頁面或下次再回來這個網站的時候,速度比第一次來的時候還要快。
這是一種快取的應用。
瀏覽器載入 HTML, JavaScript, CSS, 或圖片檔案的時候,會根據該 Response 的 HTTP Header 上面的資訊,決定這份資料要快取多久。
講人話,就是把那些資源暫時的記在你的裝置上,當你下次點開網頁要瀏覽相同資源的時候,就不用再發送一次 Request 去要資源,直接拿之前存下來的資料來顯示就可以了。
以網頁應用來說,這類型的快取是 Client 端的快取。
因為資訊的內容是存在 Client 端,也就是使用者的裝置上。
伺服器只要在回應的時候打上正確的 HTTP Header 來描述這個 Response 內容的快取時間,用戶端收到後,可以參考這個時間來決定快取的相關動作。
可以打開開發者工具,觀察網路的頁籤中,有沒有資源被標記為 快取。
(在圖片檔的處理特別明顯。)

上圖以 PChome 首頁為例。
因為快取的內容是記在用戶端,所以使用者也可以自由地把快取清除或停用。
(如上圖右上角的 停用快取 功能)
先不提使用者端的東西,在 Client 端發送一個 Request 後,經過網路的傳輸,中間可能會經過 CDN。

CDN,Content Delivery Network。
想像一個情境,你在台灣,你要瀏覽美國的某個網頁。
距離很遠,網路很慢,中間經過了無數的網路節點,你瀏覽網站的體驗很差。
如果這個時候,在台灣有一台伺服器,他幫你把你想瀏覽的那個網站內容存起來,你要瀏覽網站的時候,那台伺服器就直接把你要看的東西回應給你,而不用跟遠在天邊的那台主機互動,是不是很美好。
那台伺服器就是 CDN Server。
那如果世界上有很多台這樣的伺服器,作為服務提供者,是不是可以讓全世界的人都可以快速看到我提供的內容。
而且如果我自己的伺服器不小心壞掉,或許那些 CDN Server 還可以把內容提供給使用者,讓使用者不會感覺服務中斷。
但要自己假設 CDN 的成本太高了。
(你要怎麼在全世界都有電腦、都有網路、都有維運人員XD)
一般的做法是租用 CDN 的服務,我自己用過覺得滿順手的是 Cloudflare 跟 AWS 的 CloudFront,還有很多其他廠商都有提供 CDN 的服務,這邊不是業配就不多提了。
什麼是 CDN,可以參考這篇 Cloudflare 寫的介紹。
除了使用者端的 Cache、CDN 以外,在伺服器 Server 端也可以做快取。
例如,你的服務會需要跟第三方的服務拿資源,可是那個服務回應的很慢,那你會不會想把這些回應記下來,下次需要用到的時候直接拿之前存下來的東西,而不去跟那個很花時間的第三方服務拿。
或是 Database 的查詢有夠慢,如果你確定要拿的資料很久才會更新,那就可以考慮建立一個快取伺服器 (Cache Server),把東西存起來,並設定一個合理的過期時間 (expire time),在過期前都拿那份資料來用。
這件事可以靠 Redis 來幫我們輕鬆實現。
Redis 代表 Remote Dictionary Server,
是快速的開源記憶體內 (in-memory) 鍵值 (key-value) 資料存放區。
最常見的應用就是做快取。
因為他活在記憶體中,不像大部分的資料庫會相依於硬碟速度,記憶體的速度遠比硬碟快上許多。
Redis 的應用不只做快取,但本篇會拿來當快取伺服器。
其他應用可以參考這篇的 AWS 寫介紹。
要使用 Redis 來進行開發,最方便也最推薦的作法是,起一個 Redis Container。
使用 docker,打開 terminal 敲入:
docker run -d -p 6379:6379 redis
如果使用的是 podman,把
docker run改為podman run即可。
Redis 預設的 port 是 6379。
以上指令沒有跳出錯誤訊息就代表本機的 Redis Service 已經起起來了。
@fastify/redis 是 Fastify 官方維護的與 Redis 進行互動的 Plugin。
可以透過 npm 來安裝:
npm i @fastify/redis
接著就可以透過 server.register() 方法註冊這個 Plugin。
import fastify, { FastifyInstance } from 'fastify'
import fastifyRedis from '@fastify/redis'
const server: FastifyInstance = fastify()
server.register(fastifyRedis, {
url: 'redis://127.0.0.1',
closeClient: true
})
url 帶入 redis 的連線字串。closeClient 設為 true 表示在 app 關閉時中斷連線,預設為 false。
這個 plugin 會在 FastifyInstance 註冊一個名為 redis 的 decorator。
server.get('/hello', async (request, reply) => {
try {
const myValue = await server.redis.get('my-key')
if (myValue) {
return reply.status(200).send({ message: `Hello ${myValue}` })
}
return reply.status(200).send({ message: 'Cache Miss' })
} catch (error) {
return reply.status(500).send({ error })
}
})
透過 server.redis 拿到 Redis Client 物件,就可以使用 .get() 來拿快取的資料,或透過 .set() 來把資料丟入 Redis Server 中。
上述程式範例使用
async/awaitstyle,也可以用 callback style 來處理,
可以參考官方文件的用法。
本篇介紹了 Cache 在日常中會碰到的點,以及透過 Fastify 官方提供的 Plugin 要怎麼跟 Redis 進行互動。
Cache 是後端非常重要的元件,在系統、架構設計方面就要優先考量的重要存在。
Cache 中定義怎樣的資料才算"髒"的,哪邊應該用,哪邊不應該用,快取資料的 Expire time 要怎麼設定,等等的問題非常多。
曾經有人這麼說過:

共勉之。